package uk.ac.cam.djjb2.mygame;

import java.io.DataOutputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class ReplicaManager implements ServerComponent {
	private Map<Class<? extends ClientReplica>, Integer> classIds = new ConcurrentHashMap<Class<? extends ClientReplica>, Integer>();
	private IdManager<Replica> idManager = new IdManager<Replica>();
	private ServerNetManager netManager;
	private Map<Integer, Set<Integer>> clients = new ConcurrentHashMap<Integer, Set<Integer>>();
	
	public ServerNetManager getNetManager() {
		return netManager;
	}
	public ReplicaManager(ServerNetManager netManager) {
		this.netManager = netManager;
		netManager.addComponent(this);
	}
	private void sendClassMessage(Client client, int id, Class<? extends ClientReplica> type) throws IOException {
		DataOutputStream stream = client.createMessageStream();
		stream.writeInt(MessageIds.ID_REPLICA_CLASS);
		stream.writeInt(id);
		stream.writeUTF(type.getName());
		stream.close();
	}
	private int getClassId(Class<? extends ClientReplica> type) throws IOException {
		Integer id = classIds.get(type);
		if (id == null) {
			id = classIds.size()+1;
			classIds.put(type, id);
			
			for (Client client : netManager.getClients()) {
				sendClassMessage(client, id, type);
			}
		}
		
		return id;
	}
	private void sendDestroyMessage(Client client, Replica replica) throws IOException {
		DataOutputStream stream = client.createMessageStream();
		stream.writeInt(MessageIds.ID_REPLICA_DESTROY);
		stream.writeInt(replica.getId());
		replica.sendDestroyMessage(client, stream);
		stream.close();
	}
	private void sendCreateMessage(Client client, Replica replica) throws IOException {
		int classId = getClassId(replica.getClientClass());
		
		DataOutputStream stream = client.createMessageStream();
		stream.writeInt(MessageIds.ID_REPLICA_CREATE);
		stream.writeInt(replica.getId());
		stream.writeInt(classId);
		replica.sendCreateMessage(client, stream);
		stream.close();
	}
	private void sendUpdateMessage(Client client, Replica replica) throws IOException {
		DataOutputStream stream = client.createMessageStream();
		stream.writeInt(MessageIds.ID_REPLICA_UPDATE);
		stream.writeInt(replica.getId());
		replica.sendUpdateMessage(client, stream);
		stream.close();
	}
	public void update() throws IOException {
		for (Client client : netManager.getClients()) {
			Set<Integer> replicas = clients.get(client.getId());
			if (replicas != null) {
				for (Replica replica : idManager.getObjects()) {
					int replicaId = replica.getId();
					if (replica.shouldExist(client)) {
						if (replicas.contains(replicaId)) {
							if (replica.shouldUpdate(client)) {
								sendUpdateMessage(client, replica);
							}
						} else {
							replicas.add(replicaId);
							sendCreateMessage(client, replica);
						}
					} else {
						if (replicas.contains(replicaId)) {
							replicas.remove(replicaId);
							sendDestroyMessage(client, replica);
						}
					}
				}
			}
		}
		
		for (Replica replica : idManager.getObjects()) {
			if (replica.postUpdate())
				idManager.remove(replica.getId());
		}
	}
	public int allocateId(Replica replica) throws ReplicationException {
		if (replica.getId() != 0)
			throw new ReplicationException("Replica already has an ID!");
		return idManager.put(replica);
	}
	public Replica getReplica(int id) {
		return idManager.get(id);
	}
	public <T extends Replica> T getReplica(int id, Class<T> type) {
		return idManager.get(id, type);
	}
	@Override
	public void clientConnected(Client client) {
		clients.put(client.getId(), new HashSet<Integer>());
		
		try {
			for (Map.Entry<Class<? extends ClientReplica>, Integer> type : classIds.entrySet()) {
				sendClassMessage(client, type.getValue(), type.getKey());
			}
		} catch (IOException e) {
			// Do something...
		}
	}
	@Override
	public void clientDisconnected(Client client) {
		clients.remove(client.getId());
	}
}
